Go 语言学习-“基础语法”
Go 程序组成
package main
import "fmt"
func main() {
fmt.Println("Hello, World!")
}
package main
表示一个可独立执行的程序,每个 Go 应用程序都包含一个名为 main 的包。package main
包下可以有多个文件,但所有文件中只能有一个 main()
方法,main()
方法代表程序入口。
下一行 import "fmt"
告诉 Go 编译器这个程序需要使用 fmt 包(的函数,或其他元素),fmt 包实现了格式化 IO(输入/输出)的函数。
下一行 func main()
是程序开始执行的函数。main 函数是每一个可执行程序所必须包含的,一般来说都是在启动后第一个执行的函数(如果有 init()
函数则会先执行该函数)。
当标识符(包括常量、变量、类型、函数名、结构字段等等)以一个大写字母开头,如:Group1,那么使用这种形式的标识符的对象就可以被外部包的代码所使用(客户端程序需要先导入这个包),这被称为导出(像面向对象语言中的 public);
标识符如果以小写字母开头,则对包外是不可见的,但是他们在整个包的内部是可见并且可用的(像面向对象语言中的 protected )。
需要注意的是 {
不能单独放在一行,所以以下代码在运行时会产生错误:
package main
import "fmt"
func main()
{ // 错误,{ 不能在单独的行上
fmt.Println("Hello, World!")
}
入口函数和导包
整个 Go 程序的执行流程
执行示例:
main
import (
"std01/lib1"
"std01/lib2"
)
func main() {
lib1.Lib1Test()
lib2.Lib2Test()
}
lib1(lib2 同理)
package lib1
import "fmt"
func Lib1Test() {
fmt.Println("this is Lib1 Function")
}
func init() {
fmt.Println("this is lib1")
}
执行后打印,可以看到是 init 先执行
匿名导包和别名
在 Go 中导包不使用里面的函数,会报错,如何只导包不使用它的方法呢?(导包还是会执行 init 方法)
只需在前面加一个 _
就行了
import (
_ "std01/lib1"
"std01/lib2"
)
func main() {
lib2.Lib2Test()
}
也可以给包使用别名
import (
ali "std01/lib2"
)
func main() {
ali.Lib2Test()
}
如果不想写包名,直接使用里面的方法可以使用 .
类似 Java 的静态导入
import (
. "std01/lib2"
)
func main() {
Lib2Test()
}
但是尽量少用这种方式导包,因为当两个包都存在同一个名称的方法就会产生歧义
变量的声明方式
声明变量是靠 var
关键字,这种方式也会分配内存,不会像 Java 那样只给一个指针
声明变量的一般形式是使用 var 关键字:
var identifier type
可以一次创建多个变量
var identifier1, identifier2 type
而且类型可以不同
var ag1,ag2 = 100, "abc"
多行多变量声明
var (
ag1 int = 100
ag2 bool = true
)
实例:
package main
import "fmt"
func main() {
var a string = "Test"
fmt.Println(a)
var b, c int = 1, 2
fmt.Println(b, c)
}
如果没有初始化,则变量默认为零值。
- 数值类型(包括complex64/128)为 0
- 布尔类型为 false
- 字符串为 ""(空字符串)
- 以下几种类型为 nil:
var a *int
var a []int
var a map[string] int
var a chan int
var a func(string) int
var a error // error 是接口
可以使用 :=
来简写初始化变量
f := "Runoob"
// 等价于
var f string = "Runoob"
intVal := 1
等价于:
var intVal int
intVal =1
所以下面这样就重复创建了
var intVal int
intVal :=1 // 这时候会产生编译错误,因为 intVal 已经声明,不需要重新声明
但是注意,:=
无法声明全局变量,它只能用在函数体内
定义常量与枚举 iota
const length int = 10
Go 语言定义枚举也是通过常量来实现的
const (
MEN = 0
WOMEN = 1
UNKOWN = 2
)
可以使用 iota
关键字省略这个数字,下面这个等价于上面的
const (
MEN = iota // 0
WOMEN // 1
UNKOWN // 2
)
注意:
iota
只能用在const
框中
而且可以在这个 iota
上设置一个等式
const (
MEN = 10* iota // 0
WOMEN // 10
UNKOWN // 20
)
可以使用多个 iota
const (
// iota = 0, a = iota + 1,b = iota + 2, a = 1, b = 2
a, b = iota + 1, iota + 2
// iota = 1, c = iota + 1,d = iota + 2, c = 2, b = 3
c, d
// iota = 2, e = iota + 1,f = iota + 2, e = 3, f = 4
e, f
// iota = 3, g = iota * 2,h = iota * 3, g = 6, h = 9
g, h = iota * 2, iota * 3
// iota = 4, i = iota * 2,k = iota * 3, i = 8, k = 12
i, k
)
可以看到 iota
主要就是用于定义一个自增的规则
定义跳转标签
for、switch 或 select 语句都可以配合标签(label)形式的标识符使用,即某一行第一个以冒号(:)结尾的单词(gofmt 会将后续代码自动移至下一行)。标签的名称是大小写敏感的,为了提升可读性,一般建议使用全部大写字母
func main() {
LABEL1:
for i := 0; i <= 5; i++ {
for j := 0; j <= 5; j++ {
if j == 4 {
continue LABEL1
}
fmt.Printf("i is: %d, and j is: %d\n", i, j)
}
}
}
continue 语句指向 LABEL1,当执行到该语句的时候,就会跳转到 LABEL1 标签的位置。
可以看到当 j=4 和 j=5 的时候,没有任何输出:标签的作用对象为外部循环,因此 i 会直接变成下一个循环的值,而此时 j 的值就被重设为 0,即它的初始值。如果将 continue 改为 break,则不会只退出内层循环,而是直接退出外层循环了。
值类型和引用类型 ⭐
值类型分别有:int系列、float系列、bool、string、数组和结构体
引用类型有:指针、slice切片、管道channel、接口interface、map、函数等
判断语句
基本的使用方式就不再介绍了,这里主要介绍不一样的部分
Go 的 if 有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示:
package main
import "fmt"
func main() {
if num := 9; num < 0 {
fmt.Println(num, "is negative")
} else if num < 10 {
fmt.Println(num, "has 1 digit")
} else {
fmt.Println(num, "has multiple digits")
}
}
还有这个 switch
语句,不同的 case 之间不使用 break 分隔,默认只会执行一个 case。
switch{
case 1,2,3,4:
default:
}
如果想要执行多个 case,需要使用 fallthrough 关键字,也可用 break 终止。
switch{
case 1:
...
if(...){
break
}
fallthrough
// 此时switch(1)会执行case1和case2,但是如果满足if条件,则只执行case1
case 2:
...
case 3:
}
定义函数
定义函数和 TypeScript 有点类似,主要有个多返回值需要注意
单返回值的写法:
// 如果没有返回值可以不写
func testFunc(i int) int {
return 100 * i
}
多返回值的写法(匿名的写法):
func foo2(a int, b int) (int, int) {
return a * b, a + b
}
func main() {
ret1, ret2 := foo2(1, 2)
fmt.Println(ret1, ret2)
}
也可以使用不定长参数
func add(args ... int) (int, error){
var sum int
if len(args) == 0 {
return 0, errors.New("参数为空")
}
for _ ,arg := range args {
sum += arg
}
return sum, nil
}
具名返回值
可以给一个函数的返回值指定名字。如果指定了一个返回值的名字,则可以视为在该函数的第一行中定义了该名字的变量。
func foo3(a int, b int) (r1 int,r2 int) {
r1 = a * 100
r2 = b * 120
return
}
func main() {
ret1, ret2 := foo3(1, 2)
fmt.Println(ret1, ret2)
}
让我们写一个函数 rectProps,它接受一个矩形的长和宽,并返回该矩形的面积和周长。
package main
import (
"fmt"
)
func rectProps(length, width float64) (area, perimeter float64) {
area = length * width
perimeter = (length + width) * 2
return //no explicit return value
}
func main() {
area, perimeter := rectProps(10.8, 5.6)
fmt.Printf("Area %f Perimeter %f", area, perimeter)
}
在上面的函数中,area 和 perimeter 是命名返回值。注意 return 语句没有指定任何返回值。
因为在函数声明时已经指定 area 和 perimeter 是返回值,在遇到 return 语句时它们会自动从函数中返回。
在 Golang 中,有返回值的函数,无论是命名返回值还是普通形式的返回值,函数中必须包含 return 语句。
三个点(...)用法
第一种用法就是上面的不定长参数
第二个用法是 slice 可以被打散进行传递。
func test1(args ...string) { //可以接受任意个string参数
for _, v:= range args{
fmt.Println(v)
}
}
func main(){
var strss= []string{
"qwr",
"234",
"yui",
"cvbc",
}
test1(strss...) //切片被打散传入
}
也可以利用这个打散传递的用法合并 slice
var strss = []string{
"qwr",
"234",
"yui",
}
var strss2 = []string{
"qqq",
"aaa",
"zzz",
"zzz",
}
strss = append(strss, strss2...) //strss2的元素被打散一个个append进strss
fmt.Println(strss)
指针的使用
和 C 的指针是一样的,这里简单介绍下
值传递和 Java 是一样的
import "fmt"
func changeValue(p int) {
p = 10
}
func main() {
a := 1
changeValue(a)
fmt.Println(a) // a = 1
}
改成使用指针的方式
func changeValue(p *int) {
*p = 10
}
func main() {
a := 1
changeValue(&a)
fmt.Println(a) // a = 10
}
写一个交换两个变量值的函数
func swap(p1 *int, p2 *int) {
temp := *p1
*p1 = *p2
*p2 = temp
}
func main() {
a := 100
b := 200
swap(&a, &b)
fmt.Println(a, b)
}
二级指针,指针的指针
func main() {
a := 100
p1 := &a
var pp **int = &p1 // 指针的地址
fmt.Println(p1, pp) // 0xc0000aa058 0xc0000ce018
}
Go 也有空指针,即 nil
package main
import "fmt"
func main() {
var ptr *int
fmt.Printf("ptr 的值为 : %x\n", ptr) // ptr 的值为 : 0
}
空指针判断:
if(ptr != nil) /* ptr 不是空指针 */
if(ptr == nil) /* ptr 是空指针 */
Golang 的类型比较
Golang 不像 Java 那样专门提供了一个 equality 方法来比较对象
go 中不同类型是不能比较的,例如 int 不能和 string 进行比较;false 不能和 0 进行比较。
type a struct { // 它的变量 可以 == != 比较
name string
age int
}
type b struct { // 它的变量不可以 == != 比较
age int
m map[string]string
}
结构体只能比较是否相等,但是不能比较大小。
相同类型的结构体才能够进行比较,结构体是否相同不但与属性类型有关,还与属性顺序相关
如果 struct 的所有成员都可以比较,则该 struct 就可以通过 == 或 != 进行比较是否相等,比较时逐个项进行比较,如果每一项都相等,则两个结构体才相等,否则不相等;
注意,数组类型比较,例如 [2]int
和 [3]int
类型不同,不能进行比较。 切片不能进行比较。
更多细节参考 两个值是如何进行比较的?